﻿/* 
 * Updates the email address of a Crm 2011 User record from Active Directory.
 * By Candost Kilic 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices.AccountManagement;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Microsoft.Xrm.Sdk;
using System.DirectoryServices;

namespace UpdateCRM2011UsersFromAD
{
    class Program
    {
        static Dictionary<string, string> xRMToADFields = new Dictionary<string, string>();
        /// <summary>
        /// Setup which xRM fields map to which AD fields
        /// </summary>
        public static void SetupFieldMapping()
        {
            xRMToADFields.Add("firstname", "givenName");
            xRMToADFields.Add("lastname", "sn");
            xRMToADFields.Add("internalemailaddress", "mail");
            xRMToADFields.Add("title", "title");
            xRMToADFields.Add("address1_telephone1", "telephoneNumber");
            xRMToADFields.Add("mobilephone", "mobile");
            xRMToADFields.Add("address1_fax", "facsimileTelephoneNumber");
            xRMToADFields.Add("address1_line1", "streetAddress");
            xRMToADFields.Add("address1_city", "l");
            xRMToADFields.Add("address1_stateorprovince", "st");
            xRMToADFields.Add("address1_postalcode", "postalCode");
            xRMToADFields.Add("address1_country", "co");
        }

        /// <summary>
        /// Finds differences in email between AD and crm 2011 user record.
        /// Argument0: CRM 2011 Url ex. http://crm/crmorg
        /// Argument1: Run mode (S)can, (P)rompt, or (U)pdate
        /// </summary>
        /// <param name="args"></param>
        public static void Main(string[] args)
        {
            Console.WriteLine("Initializing...");
            SetupFieldMapping();

            // Grab the Crm Url from the first line argument. Ex. http://crmservername/crmorgname
            string crmUrl = args[0];

            // Grab the running mode (S = scan mode, P = prompt mode, U = update mode)
            // scan mode only displays the differences
            // prompt mode prompts you before updating the current record
            // and update mode updates everything without 

            string runMode;

            // Default it to Prompt mode if not entered
            if (args.Count() > 1)
                runMode = args[1];
            else
                runMode = "P";

            runMode = runMode.ToUpper();

            string crmConnectionString = "Url=" + crmUrl;
            var systemConnection = CrmConnection.Parse(crmConnectionString);

            using (var crmContext = new CrmOrganizationServiceContext(systemConnection))
            {
                // Disable caching
                crmContext.TryAccessCache(cache => cache.Mode = OrganizationServiceCacheMode.Disabled);

                // Get all the non disabled users
                var enabledUsers = (from u in crmContext.CreateQuery("systemuser") where u.GetAttributeValue<bool>("isdisabled") == false select u);

                // Grab the domain context
                Dictionary<string, string> modifiedFields = new Dictionary<string, string>();

                foreach (var currentEnabledUser in enabledUsers)
                {
                    crmContext.Detach(currentEnabledUser);

                    // Make sure the user record is valid and has a domain name.
                    if (!string.IsNullOrWhiteSpace(currentEnabledUser.GetAttributeValue<string>("domainname")))
                    {
                        // Get System User's Domain name and email address.
                        string userDomainName = currentEnabledUser.GetAttributeValue<string>("domainname");
                
                        string domainName = userDomainName.Substring(0, userDomainName.IndexOf('\\'));

                        PrincipalContext domain = new PrincipalContext(ContextType.Domain, domainName);
                
                        // Grab the crm system user's Active Directory object
                        UserPrincipal ADUserRecord = UserPrincipal.FindByIdentity(domain, userDomainName);

                        if (ADUserRecord != null)
                        {
                            bool isUserModified = false;
                            Entity userObject = new Entity("systemuser");
                            userObject.Id = currentEnabledUser.Id;
                            crmContext.Attach(userObject);

                            // Open up the actual directory entry object which contains much more data than a simple user principal object
                            using (DirectoryEntry underlyingADRecord = (DirectoryEntry)ADUserRecord.GetUnderlyingObject())
                            {
                                // Look through each field, if they do not match prompt/or update
                                foreach (var currentProperty in xRMToADFields)
                                {
                                    // Get the AD and the CRM values for the current property.
                                    string adValue = underlyingADRecord.Properties.Contains(currentProperty.Value)
                                                   ? underlyingADRecord.Properties[currentProperty.Value].Value.ToString()
                                                   : "";

                                    string crmValue = currentEnabledUser.Attributes.Contains(currentProperty.Key)
                                                    ? currentEnabledUser.GetAttributeValue<string>(currentProperty.Key)
                                                    : "";

                                    // Check if any of the fields are modified if so turn the flag to true without overriding it.
                                    if ((currentEnabledUser.GetAttributeValue<string>(currentProperty.Key) == null && !string.IsNullOrWhiteSpace(adValue))
                                        ||
                                        adValue.ToLower() != crmValue.ToLower())
                                    {
                                        // It is the first modified field of a new user so display the user we are processing.
                                        if (!isUserModified)
                                        {
                                            Console.WriteLine();
                                            Console.WriteLine("Modifications to the user: {0}", userDomainName);
                                            Console.WriteLine();
                                        }

                                        // Display the fields that need an update
                                        Console.WriteLine("Old {0}: {1}{3}New {0}: {2}{3}",
                                            currentProperty.Key,
                                            crmValue,
                                            adValue,
                                            Environment.NewLine);

                                        // Put the changes to the User Object 
                                        userObject[currentProperty.Key] = adValue;

                                        // Flag that user record has been modified
                                        isUserModified |= true;
                                    }
                                }

                                // If the record has modifications that we need to push and it is not scan mode run the update block
                                if (isUserModified && runMode != "S")
                                {
                                    // Try prompting the user if the runmode is not on update mode
                                    if (runMode != "U")
                                    {
                                        // Prompt the user if we should update this record.
                                        Console.WriteLine("Would you like to update the record Y/N/U U for Updating all the users.");
                                        var response = Console.ReadLine();

                                        response = response.ToUpper().ElementAt(0).ToString();
                                        
                                        // if they said U switch the run mode to update so they don't get consequent prompts.
                                        if (response == "U")
                                            runMode = "U";

                                        // If it is not a positive response skip updating the record, start processing the next record
                                        if (response != "U" && response != "Y")
                                            continue;
                                    }

                                    // If the code reached here it means we want to push the user object to the crm server
                                    try
                                    {
                                        crmContext.UpdateObject(userObject);
                                        crmContext.SaveChanges();

                                        Console.WriteLine("Updated User: {0}", userDomainName);
                                    }
                                    catch (Exception ex)
                                    {
                                        Console.WriteLine("Error on user {0]", userDomainName);
                                        Console.WriteLine(ex.Message);
                                    }

                                    Console.WriteLine();
                                }
                            }
                            crmContext.Detach(userObject);
                        }
                        else
                            Console.WriteLine("Warning: User {0} no longer exists in AD.", userDomainName);
                    }
                }
            }

            Console.WriteLine("Run completed, press any key to continue...");
            Console.ReadKey();
        }
    }
}